# -*- coding: utf-8 -*-
"""
This is the events module. It comprises two approaches to an
architecture based in events: the `EventsDispatcher` that honors
privacy of who triggers the events and the `Emitter` that focus on
simplicity.
"""

__license__ = "MIT License <http://www.opensource.org/licenses/mit-license.php>"
__author__ = "Tiago Cogumbreiro <cogumbreiro@users.sf.net>"
__copyright__ = "2007, Tiago Cogumbreiro"
__all__ = "EventsDispatcher", "Emitter"

import weakref

class EventSource(object):
    def __init__(self, callbacks):
        self.callbacks = callbacks
    
    def __call__(self, *args, **kwargs):
        [callback(*args, **kwargs) for callback in self.callbacks]
            

class EventsDispatcher(object):
    """
    An event dispatcher is the central events object. To use it you must first
    create an event with the ``create_event`` method, this will return an
    event source which is basically the function you'll use to trigger the
    event. After that you register the callbacks. Its usage follows:
    {{{
    >>> dispatcher = EventDispatcher()
    >>> evt_src = dispatcher.create_event("on-ring-event")
    >>> 
    >>> def callback1():
    >>>     print "riiiing!"
    >>> 
    >>> dispatcher.register("on-ring-event", callback1)
    >>> 
    >>> evt_src()
    riiing
    >>>
    }}}
    """
    def __init__(self):
        self._events = {}
        
    def create_event(self, event_name):
        self._events[event_name] = []
        return EventSource(self._events[event_name])
    
    def create_events(self, event_names, event_sources = None):
        """
        This is a utility method that creates or fills a dict-like object
        and returns it. The keys are the event names and the values are the
        event sources.
        """
        if event_sources is None:
            event_sources = {}
            
        for evt_name in event_names:
            event_sources[evt_name] = self.create_event(evt_name)
        return event_sources
    
    def event_exists(self, event_name):
        return event_name in self._events
    
    def register(self, event_name, callback):
        assert self.event_exists(event_name)
        self._events[event_name].append(callback)
    
    def unregister(self, event_name, callback):
        self._events[event_name].remove(callback)


class EventConnection(object):
    """
    This object represents the connection to the event.
    Once you loose the reference to this object the callback is automatically
    disconnected.
    """

class Emitter(object):
    """
    The `Emitter` object is based on `gobject.GObject`. You create an
    emiter object like so:
    {{{
    >>> e = Emitter("evt1", "evt3", "evt2")
    >>> evts = e.events
    ("evt1", "evt2", "evt3")
    }}}
    
    The property `events` is a sorted tuple of the event names. This
    class contains three methods: the `connect`, the `bound_connect`,
    and the `emit`.

    The `connect` method is used to bind a callback (callable object)
    to an event. When callback is bound to an event, a reference that
    represents its connection is returned. When the reference is
    destroyed, your callback will no longer be connected to the event,
    thus will not be called when the event is emitted afterwards. The
    following example shows how the connection to an event works:
    {{{
    >>> e = Emitter("evt1")
    >>> ref = e.connect("evt1", my_callback)
    >>> e.emit("evt1") # will trigger the `my_callback`
    >>> del ref
    >>> e.emit("evt1") # will not trigger `my_callback`
    }}}
    
    The `bound_connect` connect method is an utility method that does
    the same thing as does the method `connect`, but does not return a
    the connection, hence the connection exists as long as the
    `Emitter` instance exists::
    {{{
    >>> e = Emitter("evt1")
    >>> e.bound_connect("evt1", my_callback)
    }}}

    The `emit` method is used to iterate over all listeners of a
    certain event and call them with the arguments sent to the `emit`
    method. The following example emits the event `evt1` and calls
    each callback with arguments `1`, `2` and `3`:
        
    {{{
    >>> e = Emitter("evt1")
    >>> e.emit("evt1", 1, 2, 3)
    }}}
    """
    def __init__(self, *events):
        """
        The name of the events you want to initialize the emiter object with.
        """
        self._events = dict((event, weakref.WeakKeyDictionary()) \
                            for event in events)
        self._bound = []
        self.events = self._events.keys()
        self.events.sort()
        self.events = tuple(self.events)
    
    def connect(self, event, callback):
        """
        Connect a callback to an event. This will return an object
        that represents the connection to the event, if you loose its
        reference the connection will also be lost.
        """
        evt_conn = EventConnection()
        self._events[event][evt_conn] = callback
        return evt_conn
        
    def connect_bound(self, event, callback):
        """
        This is the same thing an extension of the `connect` method
        but the connection to the event is only removed when this
        emitter is destroyed.
        """
        self._bound.append(self.connect(event, callback))
    
    def emit(self, event, *args, **kwargs):
        """
        Emits the event. Iterates over all the callbacks available on
        a certain event and calls them with the given arguments on the
        `emit` method itself.
        """
        [cb(*args, **kwargs) for cb in self._events[event].values()]

